﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using PaperRenderingInterfaces;
using DeepZoomPivotConstructor.PivotLib;
using System.ComponentModel.Composition;
using DeepZoomPivotConstructor.ErrorAndLoggingInterfaces;

namespace PaperRenderingEngine
{
    /// <summary>
    /// Does the basics of rendering things
    /// </summary>
    [Export(typeof(IPaperRenderEngine))]
    public class RenderEngine : IPaperRenderEngine
    {
        /// <summary>
        /// List of dudes that know what to do.
        /// </summary>
        [ImportMany(typeof(IPaperRender))]
        private Lazy<IPaperRender, PaperRenderAttribute>[] Renderers = null;

        [Import(AllowDefault=true)]
        private IErrorLogger Logger;

        /// <summary>
        /// Keep track of any items that we have already rendered. They are indexed by the
        /// render type name and a list of addresses.
        /// </summary>
        private Dictionary<string, List<string>> _alreadyRenderedItems = new Dictionary<string, List<string>>();

        /// <summary>
        /// Look up the rendering, and if we can find it, run it!
        /// </summary>
        /// <param name="renderTypeName"></param>
        /// <param name="renderAddress"></param>
        /// <returns></returns>
        public IEnumerable<PivotImage> Render(string renderTypeName, string renderAddress)
        {
            ///
            /// Basic tests...
            /// 

            if (Renderers == null || renderTypeName == null || renderAddress == null)
            {
                return Enumerable.Empty<PivotImage>();
            }

            ///
            /// Next, see if this thing has been rendered already in this run (our inline cache). If
            /// so then return nothing.
            /// 

            if (_alreadyRenderedItems.ContainsKey(renderTypeName))
            {
                if (_alreadyRenderedItems[renderTypeName].Contains(StripTrailingSlash(renderAddress)))
                {
                    return Enumerable.Empty<PivotImage>();
                }
            }

            ///
            /// See if we can find the proper rendering dude. Return the rendering if we can.
            /// 

            var engines = from r in Renderers
                          where (r.Metadata.RenderEngineName == renderTypeName)
                          select r;

            var engine = engines.FirstOrDefault();
            if (engine == null)
            {
                if (Logger != null)
                {
                    Logger.LogMessage("PivotRenderEngine", "Don't know how to render type '" + renderTypeName + "' for address '" + renderAddress + "'.");
                }
                return Enumerable.Empty<PivotImage>();
            }

            ///
            /// If we have gotten this far, then we can assume that we are going
            /// to render it and we shouldn't re-try during this run
            /// 

            if (!_alreadyRenderedItems.ContainsKey(renderTypeName))
            {
                _alreadyRenderedItems[renderTypeName] = new List<string>();
            }
            _alreadyRenderedItems[renderTypeName].Add(StripTrailingSlash(renderAddress));

            ///
            /// Protect against throws during the render
            /// 

            try
            {
                return ProtectedIterator(engine.Value.Render(renderAddress), "plugin type '" + renderTypeName + "' - address '" + renderAddress + "')");
            }
            catch (Exception e)
            {
                if (Logger != null)
                {
                    Logger.LogMessage("PivotRenderEngine", "Failed during plugin call (plugin type '" + renderTypeName + "' - address '" + renderAddress + "').", e);
                }
                return Enumerable.Empty<PivotImage>();
            }
        }

        /// <summary>
        /// If there is a trailing slashs on this string, remove it. This is only done
        /// for what looks like a http path.
        /// </summary>
        /// <param name="renderAddress"></param>
        /// <returns></returns>
        private string StripTrailingSlash(string renderAddress)
        {
            if (!renderAddress.StartsWith("http"))
            {
                return renderAddress;
            }

            string result = renderAddress;
            while (result.EndsWith("/"))
            {
                result = result.Substring(0, result.Length - 1);
            }

            return result;
        }

        /// <summary>
        /// Iterate through the list - if we bomb, record it, but don't
        /// totally kill everyone off!
        /// 
        /// Thsi routine is tourchured because the compiler can't lift a yield return out of a try block.
        /// </summary>
        /// <param name="iEnumerable"></param>
        /// <returns></returns>
        private IEnumerable<PivotImage> ProtectedIterator(IEnumerable<PivotImage> iEnumerable, string message)
        {
            PivotImage image = null;
            IEnumerator<PivotImage> index = null;

            ///
            /// Get out the enumerator
            /// 

            try
            {
                index = iEnumerable.GetEnumerator();
            }
            catch (Exception e)
            {
                if (Logger != null)
                {
                    Logger.LogMessage("PivotRenderingEngine", "Failed to get enumerator for plugin results " + message, e);
                }
            }

            ///
            /// Now, loop until we are done!
            /// 

            bool goodItem = false;
            do
            {
                goodItem = false;
                try
                {
                    goodItem = index.MoveNext();
                    if (goodItem)
                    {
                        image = index.Current;
                    }

                }
                catch (Exception e)
                {
                    if (Logger != null)
                    {
                        Logger.LogMessage("PivotRenderingEngine", "Failed MoveNext of plugin iterator: " + message, e);
                    }
                }

                if (goodItem)
                {
                    yield return image;
                }
            } while (goodItem);
        }
    }
}
